/*  
 *  Copyright 2006 Paul Jack.
 *
 *  This file is part of the Dex compiler suite. 
 *  
 *  Dex is free software; you can redistribute it and/or modify it
 *  under the terms of the GNU General Public License as published by the
 *  Free Software Foundation; either version 2 of the License, or (at your
 *  option) any later version.
 *  
 *  Dex is distributed in the hope that it will be useful, but
 *  WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
 *  Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
 */
package dex.compiler.model.program;


import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import dex.compiler.model.definition.Container;
import dex.compiler.model.definition.Definition;
import dex.compiler.model.definition.Modifier;
import dex.compiler.model.definition.Unit;
import dex.compiler.model.definition.UserType;


/**
 * Utilities for working with Program implementations.
 */
public class Programs {


	/**
	 * The name of the core unit.  This unit is automatically
	 * imported, and defines things like Object and String.
	 */
	final public static String CORE = "dex:core";


	/**
	 * Searches the given program for definitions.
	 * 
	 * @param program    the program that owns the unit
	 * @param requestor  the unit to search
	 * @param searcher   the search criteria
	 * @return   the list of paths leading to definitions that match
	 */
	public static List<Path> search(Program program, Unit requestor, Searcher searcher) {
		if (program == null) {
			throw new IllegalArgumentException("program may not be null");
		}
		if (requestor == null) {
			throw new IllegalArgumentException("requestor may not be null");
		}
		if (searcher == null) {
			throw new IllegalArgumentException("searcher may not be null");
		}
		List<Path> results = new ArrayList<Path>();
		String target = searcher.getQualifiedUnit();
		if (target != null) {
			Unit unit = program.loadProfile(target);
			search(results, program, unit, unit, searcher);
		} else {
			// Note it's legal for a Dex program to have duplicate imports.
			Set<String> imports = new HashSet<String>(requestor.getImportedUnitNames());
			imports.add(requestor.getName());
			imports.add(CORE);
			
			for (String name: imports) {
				Unit profile = program.loadProfile(name);
				if (profile != null) {
					search(results, program, profile, profile, searcher);
				}
			}
		}
		return results;
	}
	
	
	/**
	 * Searches a class or record for member definitions.
	 * 
	 * @param program    the program that owns the unit
	 * @param requestor  the unit to search
	 * @param searcher   the search criteria
	 * @return   the list of paths leading to definitions that match
	 */
	public static List<Path> search(Program program, Unit unit, UserType container, Searcher searcher) {
		List<Path> results = new ArrayList<Path>();
		search(results, program, unit, container, searcher);
		return results;
	}


	/**
	 * Searches a particular unit for definitions.
	 * 
	 * @param results  the list of results to append to
	 * @param program  the program to search
	 * @param search   the particular unit to search
	 * @param searcher  the search criteria
	 */
	private static void search(List<Path> results, Program program, Unit unit, Container search, Searcher searcher) {
		if (unit == null) {
			return;
		}
		if (search == null) {
			return;
		}
		for (Definition def: search.getDefinitions()) {
			if (searcher.matches(unit, def)) {
				results.add(new Path(program, unit, search, def));
			}
		}
	}


	/**
	 * Checks that the given definition is visible to the given unit.
	 * 
	 * @param unit  the unit to check
	 * @param path  the path to check
	 * @return  true if that definition is visible from the current
	 *   unit; false otherwise.
	 */
	public static boolean checkVisibility(Unit unit, Path path) {
		Modifier mod = path.getDefinition().getModifier();
		if (mod == Modifier.PUBLIC) {
			return true;
		}
		if (mod == Modifier.PROTECTED) {
			// This is actually a problem; but it should be reported when
			// the offending definition is checked.
			return true;
		}
		if ((mod == Modifier.PRIVATE) && (path.getUnit() == unit)) {
			return true;
		}
		assert mod == Modifier.DEFAULT;
		String thisParent = getParentUnit(unit.getName());
		String pathParent = getParentUnit(path.getUnit().getName());
		return thisParent.equals(pathParent);
	}


	private static String getParentUnit(String unitName) {
		int p = unitName.lastIndexOf(':');
		if (p < 0) {
			return "";
		}
		return unitName.substring(0,p);
	}
}
